package com.eight.cloud.logging.aspect;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.eight.cloud.logging.model.vo.OperationLogVo;
import com.eight.cloud.core.global.model.LoginUser;
import com.eight.cloud.core.global.result.Result;
import com.eight.cloud.core.utils.HttpUtils.HttpContextUtils;
import com.eight.cloud.core.utils.supply.UserContext;
import com.eight.cloud.logging.annotations.OperationLog;
import com.eight.cloud.logging.interfaces.OperationLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;

import static com.eight.cloud.core.global.constant.StrConstant.*;

/**
 * 操作日志切面
 *
 * @ClassName: OpenApiAspect
 * @Author: TXC
 * @Date: 2025-02-14 10:55
 **/
@Slf4j
@Aspect
public class OperationLogAspect {
    private final ThreadLocal<OperationLogVo> operationLogThreadLocal = new ThreadLocal<>();
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    private final OperationLogService operationLogService;
    public OperationLogAspect(OperationLogService operationLogService) {
        this.operationLogService = operationLogService;
    }

    /**
     * 切入连接点，使用固定 controller层下的所有文件
     */
    @Pointcut(value = "@annotation(com.eight.cloud.logging.annotations.OperationLog)")
    public void logPointcut() {
    }

    /**
     * 日志记录
     */
    @Before("logPointcut()")
    public void beforeLogger(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        OperationLog methodApiOperation = methodSignature.getMethod().getDeclaredAnnotation(OperationLog.class);
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        OperationLogVo operationLog = new OperationLogVo();
        LoginUser loginUser = UserContext.getUser();
        // 创建人信息请根据实际项目获取方式获取
        if (ObjectUtil.isNotNull(loginUser)) {
            operationLog.setCreateBy(loginUser.getUuid());
        }
        operationLog.setModule(methodApiOperation.module());
        operationLog.setEvents(methodApiOperation.events());
        operationLog.setStartTime(LocalDateTime.now());
        if (ObjectUtil.isNotNull(request)) {
            operationLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
            operationLog.setRequestParams(formatParams(request.getParameterMap()));
            operationLog.setRequestMethod(request.getMethod());
            operationLog.setRequestIp(ServletUtil.getClientIP(request));
            operationLog.setServerAddress(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort());
            String userAgentStr = request.getHeader("User-Agent");
            operationLog.setUserAgent(userAgentStr);
            UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
            if (ObjectUtil.isNotNull(userAgent)) {
                operationLog.setDeviceName(userAgent.getOs().getName());
                operationLog.setBrowserName(userAgent.getBrowser().getName());
            }
        }
        operationLogThreadLocal.set(operationLog);
        log.info("开始计时: {}  URI: {}  IP: {}", operationLog.getStartTime(), operationLog.getRequestUri(),
                operationLog.getRequestIp());
    }

    /**
     * 请求后置通知，请求完成会进入到这个方法
     *
     * @param result 响应结果json
     */
    @AfterReturning(value = "logPointcut()", returning = "result")
    public void afterReturningLogger(Object result) {
        OperationLogVo operationLog = operationLogThreadLocal.get();
        operationLog.setEndTime(LocalDateTime.now());
        if (result instanceof Result) {
            Result<?> res = (Result<?>) result;
            operationLog.setExecuteTime(ChronoUnit.MILLIS.between(operationLog.getStartTime(), operationLog.getEndTime()));
            operationLog.setIsException(res.isSuccess() ? 0 : 1);
            if (!res.isSuccess()) {
                operationLog.setExceptionInfo(res.getMessage());
            }
        }
        threadPoolTaskExecutor.getThreadPoolExecutor().execute(new SaveLogThread(operationLog, operationLogService));
        operationLogThreadLocal.remove();
        log.info("计时结束: {}  用时: {}ms  URI: {}", operationLog.getEndTime(), operationLog.getExecuteTime(), operationLog.getRequestUri());
    }

    /**
     * 异常通知，请求异常会进入到这个方法
     */
    @AfterThrowing(value = "logPointcut()", throwing = "throwable")
    public void throwingLogger(Throwable throwable) {
        OperationLogVo operationLog = operationLogThreadLocal.get();
        operationLog.setEndTime(LocalDateTime.now());
        operationLog.setExecuteTime(ChronoUnit.MILLIS.between(operationLog.getStartTime(), operationLog.getEndTime()));
        operationLog.setIsException(1);
        operationLog.setExceptionInfo(throwable.getMessage());
        threadPoolTaskExecutor.getThreadPoolExecutor().execute(new SaveLogThread(operationLog, operationLogService));
        operationLogThreadLocal.remove();
        log.info("计时结束: {}  用时: {}ms  URI: {} ERROR: {}", operationLog.getEndTime(), operationLog.getExecuteTime(), operationLog.getRequestUri(), throwable.getMessage());
    }

    /**
     * 格式化参数
     *
     * @param parameterMap
     */
    private String formatParams(Map<String, String[]> parameterMap) {
        if (parameterMap == null) {
            return null;
        }
        StringBuilder params = new StringBuilder();
        for (Map.Entry<String, String[]> param : (parameterMap).entrySet()) {
            if (params.length() != 0) {
                params.append(AMPERSAND);
            }
            params.append(param.getKey()).append(EQUAL);
            if (StrUtil.endWithIgnoreCase(param.getKey(), PASSWORD)) {
                params.append(ASTERISK);
            } else if (param.getValue() != null) {
                params.append(ArrayUtil.join(param.getValue(), COMMA));
            }
        }
        return params.toString();
    }

    /**
     * 保存日志线程
     */
    private static class SaveLogThread extends Thread {
        private final OperationLogVo operationLog;
        private final OperationLogService operationLogService;

        public SaveLogThread(OperationLogVo operationLog, OperationLogService operationLogService) {
            this.operationLog = operationLog;
            this.operationLogService = operationLogService;
        }

        @Override
        public void run() {
            operationLog.setCreateTime(LocalDateTime.now());
            operationLogService.record(operationLog);
        }
    }
}
